package editor.graphics;

import java.awt.event.*;
import java.util.HashMap;
import java.nio.FloatBuffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;

import moltencore.game.loader.LevelSprite;
import spark.graphics.*;
import spark.graphics.scenegraph.SceneGraph;
import spark.graphics.scenegraph.TranslationNode;
import spark.graphics.scenegraph.SpriteNode;
import spark.exception.LoadingException;
import spark.math.Vector3;
import blazingbubble.exception.StartupException;
import org.apache.log4j.Logger;
import editor.logic.LevelSpriteFactory;
import editor.logic.Editor;
import moltencore.game.bubble.Bubble;
import moltencore.game.Grid;

public class Scene implements GLEventListener, KeyListener, MouseMotionListener, MouseWheelListener, LevelSpriteFactoryHandler {
	private static Logger logger = Logger.getLogger(Scene.class);

	private SceneGraph graph = new SceneGraph();

	private Camera camera = new Camera();
	private float cameraSpeed = 10.00f;
	private boolean w = false;
	private boolean a = false;
	private boolean s = false;
	private boolean d = false;
	private boolean q = false;
	private boolean e = false;
	private float walkDistance = 0;
	private float passedMS = 0;
	private int mouseX = -10000;
	private int mouseY = -10000;

	private LevelSprite cursor;
	private boolean cursorSnap = false;
	private Vector3 cursorSnapPosition = new Vector3();
	private HashMap<String, LevelSpriteFactory> levelSpriteFactories = new HashMap<String, LevelSpriteFactory>();
	private Editor editor;
	private LevelRenderer levelRenderer;

	private boolean bubbleGridSnap = false;
	private boolean wallGridSnap = false;
	private Grid bubbleGrid = new Grid();
	private int gridPositionX;
	private int gridPositionY;
	private float cursorDepth;

	private boolean renderMisc = false;
	private boolean renderPush = false;
	private boolean renderFill = false;

	public Scene() {
	}

	@Override
	public void display(GLAutoDrawable drawable) {
		Renderer.getInstance().getGl().glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		Renderer.getInstance().getGl().glLoadIdentity();

		camera.update();
		renderMouseCollisionPlane();
		renderLevel();

		renderCursor();
		graph.render();
	}

	public void doFrame(float elapsedSeconds) {
		passedMS += elapsedSeconds;
		if(passedMS > 1000) {
			//System.out.println("FPS " + Clock.getInstance().getFramesPerSecond());
			passedMS = 0;
		}
		if(s) {
			camera.jump(-cameraSpeed * elapsedSeconds);
		}
		if(w) {
			camera.jump(cameraSpeed * elapsedSeconds);
		}
		if(walkDistance != 0) {
			camera.walk(walkDistance * 3 * cameraSpeed * elapsedSeconds);
			walkDistance = 0;
		}
		if(d) {
			camera.strafe(-cameraSpeed * elapsedSeconds);
		}
		if(a) {
			camera.strafe(cameraSpeed * elapsedSeconds);
		}
		if(q) {
		}
		if(e) {
		}
	}

	public void setCursor(LevelSprite cursor) {
		this.cursor = cursor;
	}

	private void renderLevel() {
		if(editor == null || editor.getCurrentlevel() == null) {
			return;
		}
		levelRenderer.renderLevel(editor.getCurrentlevel(), renderMisc, renderPush, renderFill);
	}

	private void renderCursor() {
		if(cursor == null) {
			return;
		}
		if(cursor.getRenderable() == null) {
			try {
				cursor = levelSpriteFactories.get(cursor.getSpriteMap() + ":" + cursor.getTheme()).getLevelSprite(cursor.getId());
			}
			catch(LoadingException e1) {
				throw new RuntimeException(e1);
			}
		}
		//store the z position
		Vector3 projectedPosition = new Vector3();
		getMouse3D(projectedPosition);
		if(bubbleGridSnap) {
			handleBubbleGridSnap(projectedPosition);
		}
		else if(wallGridSnap) {
			handleWallGridSnap(projectedPosition);
		}
		else if(cursorSnap) {
			handleCursorSnap(projectedPosition);
		}
		cursor.getRenderable().getPosition().x = projectedPosition.x;
		cursor.getRenderable().getPosition().y = projectedPosition.y;
		cursor.getRenderable().getPosition().z = cursorDepth;
		Vector3 color = null;
		if(cursor.getId().startsWith(PhysicalSpritePanel.WALL_PREFIX)) {
			color = new Vector3(0, 1f, 0);
		}
		else if(cursor.getId().equals(PhysicalSpritePanel.CRITICAL_AREA)) {
			color = new Vector3(1f, 1f, 0);
		}
		else if(cursor.getId().equals(PhysicalSpritePanel.RESTRICTED_AREA)) {
			color = new Vector3(1f, 0, 0);
		}
		else if(cursor.getId().equals(PhysicalSpritePanel.KILL_AREA)) {
			color = new Vector3(0, 0, 0);
		}
		if( color != null ) {
			Renderer.getInstance().getGl().glDisable(GL.GL_TEXTURE_2D);
			Renderer.getInstance().getGl().glBegin(GL.GL_LINES);
			Renderer.getInstance().getGl().glColor3f(color.x, color.y, color.z);
			if( cursor.getId().equals(PhysicalSpritePanel.WALL_HORIZONTAL) ||
				cursor.getId().equals(PhysicalSpritePanel.CRITICAL_AREA) ||
				cursor.getId().equals(PhysicalSpritePanel.RESTRICTED_AREA) ||
				cursor.getId().equals(PhysicalSpritePanel.KILL_AREA)) {
				Renderer.getInstance().getGl().glVertex3f(-1000, projectedPosition.y, Editor.WALL_DEPTH);
				Renderer.getInstance().getGl().glVertex3f(1000, projectedPosition.y, Editor.WALL_DEPTH);
			}
			else if(cursor.getId().equals(PhysicalSpritePanel.WALL_VERTICAL)) {
				Renderer.getInstance().getGl().glVertex3f(projectedPosition.x, -1000, Editor.WALL_DEPTH);
				Renderer.getInstance().getGl().glVertex3f(projectedPosition.x, 1000, Editor.WALL_DEPTH);
			}
			Renderer.getInstance().getGl().glColor3f(1, 1, 1);
			Renderer.getInstance().getGl().glEnd();
			Renderer.getInstance().getGl().glEnable(GL.GL_TEXTURE_2D);
			return;
		}
		TranslationNode cursorTranslationNode = new TranslationNode(cursor.getRenderable().getPosition());
		SpriteNode cursorSpriteNode = new SpriteNode();
		cursorSpriteNode.registerForRendering(cursor.getRenderable());
		cursorTranslationNode.addChild(cursorSpriteNode);
		graph.getRoot().addChild(cursorTranslationNode);
	}

	private void handleBubbleGridSnap(Vector3 projectedPosition) {
		Bubble bubble = new Bubble(null, (short) 0);
		bubble.getPosition().x = projectedPosition.x;
		bubble.getPosition().y = projectedPosition.y;
		if(bubbleGrid == null) {
			bubbleGrid = new Grid();
		}
		bubbleGrid.snapToGrid(bubble);
		gridPositionX = bubble.getGridPosition().x;
		gridPositionY = bubble.getGridPosition().y;
		bubbleGrid.clearBubble(gridPositionX, gridPositionY);
		projectedPosition.x = bubble.getPosition().x;
		projectedPosition.y = bubble.getPosition().y;
	}

	private void handleWallGridSnap(Vector3 projectedPosition) {
		Bubble bubble = new Bubble(null, (short) 0);
		bubble.getPosition().x = projectedPosition.x;
		bubble.getPosition().y = projectedPosition.y - 2 * Grid.XINDENT;
		bubbleGrid.snapToGrid(bubble);
		gridPositionX = bubble.getGridPosition().x;
		gridPositionY = bubble.getGridPosition().y;
		bubbleGrid.clearBubble(gridPositionX, gridPositionY);
		projectedPosition.x = bubble.getPosition().x;
		projectedPosition.y = bubble.getPosition().y + Grid.XINDENT;
	}

	private void handleCursorSnap(Vector3 projectedPosition) {
		//snap x
		float distanceX = (projectedPosition.x - cursorSnapPosition.x) / cursor.getRenderable().getDimensions().x;
		float positioning = distanceX - (int) distanceX;
		int gridPositionX = (int) distanceX;
		if(positioning < -0.5) {
			gridPositionX -= 1;
		}
		else if(positioning >= 0.5) {
			gridPositionX += 1;
		}
		projectedPosition.x = cursorSnapPosition.x + gridPositionX * cursor.getRenderable().getDimensions().x;
		//snap y
		float distanceY = (projectedPosition.y - cursorSnapPosition.y) / cursor.getRenderable().getDimensions().y;
		positioning = distanceY - (int) distanceY;
		int gridPositionY = (int) distanceY;
		if(positioning <= -0.5) {
			gridPositionY -= 1;
		}
		else if(positioning > 0.5) {
			gridPositionY += 1;
		}
		projectedPosition.y = cursorSnapPosition.y + gridPositionY * cursor.getRenderable().getDimensions().y;
	}

	private void renderMouseCollisionPlane() {
		float span = 10000;
		Renderer.getInstance().getGl().glPushMatrix();
		Renderer.getInstance().getGl().glBegin(GL.GL_QUADS);
		Renderer.getInstance().getGl().glTexCoord2f(0, 0);
		Renderer.getInstance().getGl().glVertex3f(-span, -span, -.01f);

		Renderer.getInstance().getGl().glTexCoord2f(0, 0);
		Renderer.getInstance().getGl().glVertex3f(span, -span, -.01f);

		Renderer.getInstance().getGl().glTexCoord2f(0, 0);
		Renderer.getInstance().getGl().glVertex3f(span, span, -.01f);

		Renderer.getInstance().getGl().glTexCoord2f(0, 0);
		Renderer.getInstance().getGl().glVertex3f(-span, span, -.01f);

		Renderer.getInstance().getGl().glEnd();
		Renderer.getInstance().getGl().glPopMatrix();
	}

	@Override
	public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
		System.out.println("Display changed");
	}

	@Override
	public void init(GLAutoDrawable drawable) {
		Renderer.getInstance().initialize(drawable);
		Renderer.getInstance().setVSync(true);

		Renderer.getInstance().getGl().glEnable(GL.GL_TEXTURE_2D);
		Renderer.getInstance().getGl().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
		Renderer.getInstance().getGl().glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR);

		Renderer.getInstance().getGl().glEnable(GL.GL_BLEND);
		Renderer.getInstance().getGl().glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA);


		if(levelRenderer == null) {
			for(String factoryIdent : levelSpriteFactories.keySet()) {
				if(factoryIdent.contains(Window.PHYSICAL_OBJECTS_SPRITE_MAP + ":" + Window.PHYSICAL_OBJECTS_THEME)) {
					try {
						levelRenderer = new LevelRenderer(graph, levelSpriteFactories.get(factoryIdent), levelSpriteFactories);
					}
					catch(LoadingException e1) {
						throw new AssertionError(e1);
					}
				}
			}
		}


		resetCamera();
	}

	@Override
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
		Renderer.getInstance().resizeScenePerspectiveFov();
	}

	public void shutdown() {
	}

	@Override
	public void keyPressed(KeyEvent event) {
		if(event.getKeyCode() == KeyEvent.VK_S) {
			s = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_W) {
			w = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_A) {
			a = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_D) {
			d = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_Q) {
			q = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_E) {
			e = true;
		}
		if(event.getKeyCode() == KeyEvent.VK_CONTROL) {
			if(cursor != null && cursor.getRenderable() != null) {
				cursorSnapPosition.copy(cursor.getRenderable().getPosition());
				cursorSnap = !cursorSnap;
			}
		}
	}

	@Override
	public void keyReleased(KeyEvent event) {
		if(event.getKeyCode() == KeyEvent.VK_S) {
			s = false;
		}
		if(event.getKeyCode() == KeyEvent.VK_W) {
			w = false;
		}
		if(event.getKeyCode() == KeyEvent.VK_A) {
			a = false;
		}
		if(event.getKeyCode() == KeyEvent.VK_D) {
			d = false;
		}
		if(event.getKeyCode() == KeyEvent.VK_Q) {
			q = false;
		}
		if(event.getKeyCode() == KeyEvent.VK_E) {
			e = false;
		}
	}

	@Override
	public void keyTyped(KeyEvent event) {
	}

	@Override
	public void mouseDragged(MouseEvent arg0) {
	}

	@Override
	public void mouseMoved(MouseEvent event) {
		mouseX = event.getX();
		mouseY = event.getY();
	}

	private void startup() throws StartupException {
	}

	@Override
	public void levelSpriteFactoryCreated(LevelSpriteFactory levelSpriteFactory) {
		if(!containsFactory(levelSpriteFactory.getIdent())) {
			levelSpriteFactories.put(levelSpriteFactory.getIdent(), levelSpriteFactory);
		}
	}

	@Override
	public boolean containsFactory(String factoryIdent) {
		return levelSpriteFactories.containsKey(factoryIdent);
	}

	@Override
	public LevelSpriteFactory getFactory(String factoryIdent) {
		return levelSpriteFactories.get(factoryIdent);
	}

	private void getMouse3D(Vector3 mousePosition) {
		int viewport[] = new int[4];
		double[] proj = new double[16];
		double[] model = new double[16];
		Renderer.getInstance().getGl().glGetIntegerv(GL.GL_VIEWPORT, viewport, 0);
		Renderer.getInstance().getGl().glGetDoublev(GL.GL_PROJECTION_MATRIX, proj, 0);
		Renderer.getInstance().getGl().glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0);
		FloatBuffer fb = ByteBuffer.allocateDirect(4).order(ByteOrder.nativeOrder()).asFloatBuffer();
		Renderer.getInstance().getGl().glReadPixels(mouseX, Renderer.getInstance().getRenderAreaHeight() - mouseY, 1, 1, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT, fb);
		fb.rewind();
		double[] mousePosArr = new double[4];
		Renderer.getInstance().getGlu().gluUnProject((double) mouseX, Renderer.getInstance().getRenderAreaHeight() - (double) mouseY, (double) fb.get(0), model, 0, proj, 0, viewport, 0, mousePosArr, 0);
		mousePosition.x = (float) mousePosArr[0];
		mousePosition.y = (float) mousePosArr[1];
		mousePosition.z = (float) mousePosArr[2];
	}

	private void resetCamera() {
		camera.reset();
		camera.walk(-21.66f);
	}

	public LevelSprite getCursor() {
		return cursor;
	}

	public void addEditor(Editor editor) {
		this.editor = editor;
	}

	public boolean isBubbleGridSnap() {
		return bubbleGridSnap;
	}

	public void setBubbleGridSnap(boolean bubbleGridSnap) {
		this.bubbleGridSnap = bubbleGridSnap;
	}

	public void setWallGridSnap(boolean wallGridSnap) {
		this.wallGridSnap = wallGridSnap;
	}

	public int getGridPositionX() {
		return gridPositionX;
	}

	public int getGridPositionY() {
		return gridPositionY;
	}

	public void setCursorDepth(float depth) {
		cursorDepth = depth;
	}

	@Override
	public void mouseWheelMoved(MouseWheelEvent e) {
		walkDistance = -e.getWheelRotation();
	}

	public void setRenderMisc(boolean render) {
		renderMisc = render;
	}

	public void setRenderPush(boolean render) {
		renderPush = render;
	}

	public void setRenderFill(boolean renderFill) {
		this.renderFill = renderFill;
	}
}